/*
  jQuery Ketchup Plugin - Tasty Form Validation
  ---------------------------------------------
  
  Version 0.3.1 - 12. Jan 2011
  
  Copyright (c) 2011 by Sebastian Senf:
    http://mustardamus.com/
    http://usejquery.com/
    http://twitter.com/mustardamus

  Dual licensed under the MIT and GPL licenses:
    http://www.opensource.org/licenses/mit-license.php
    http://www.gnu.org/licenses/gpl.html

  Demo: http://demos.usejquery.com/ketchup-plugin/
  Repo: http://github.com/mustardamus/ketchup-plugin
*/

(function(g) {
	g.ketchup = {
		defaults: {
			attribute: "data-validate",
			validateIndicator: "validate",
			eventIndicator: "on",
			validateEvents: "blur",
			validateElements: ["input", "textarea", "select"],
			createErrorContainer: null,
			showErrorContainer: null,
			hideErrorContainer: null,
			addErrorMessages: null
		},
		dataNames: {
			validationString: "ketchup-validation-string",
			validations: "ketchup-validations",
			events: "ketchup-events",
			elements: "ketchup-validation-elements",
			container: "ketchup-container"
		},
		validations: {},
		helpers: {},
		validation: function(b, c, d, h) {
			var j;
			if (typeof c == "function") c = c;
			else {
				j = c;
				c = d
			}
			this.validations[b] = {
				message: j,
				func: c,
				init: h ||
				function() {}
			};
			return this
		},
		message: function(b, c) {
			this.addMessage(b, c);
			return this
		},
		messages: function(b) {
			for (name in b) this.addMessage(name, b[name]);
			return this
		},
		addMessage: function(b, c) {
			if (this.validations[b]) this.validations[b].message = c
		},
		helper: function(b, c) {
			this.helpers[b] = c;
			return this
		},
		init: function(b, c, d) {
			this.options = c;
			var h = this;
			c = this.initFunctions().initFields(b, d);
			c.each(function() {
				var j = g(this);
				h.bindValidationEvent(b, j).callInitFunctions(b, j)
			});
			b.data(this.dataNames.elements, c);
			this.bindFormSubmit(b)
		},
		initFunctions: function() {
			var b = this.options,
				c = ["createErrorContainer", "showErrorContainer", "hideErrorContainer", "addErrorMessages"];
			for (f = 0; f < c.length; f++) {
				var d = c[f];
				b[d] || (b[d] = this[d])
			}
			return this
		},
		initFields: function(b, c) {
			var d = this,
				h = this.dataNames,
				j = g(!c ? this.fieldsFromForm(b) : this.fieldsFromObject(b, c));
			j.each(function() {
				var l = g(this),
					m = d.extractValidations(l.data(h.validationString), d.options.validateIndicator);
				l.data(h.validations, m)
			});
			return j
		},
		callInitFunctions: function(b, c) {
			var d = c.data(this.dataNames.validations);
			for (i = 0; i < d.length; i++) d[i].init.apply(this.helpers, [b, c])
		},
		fieldsFromForm: function(b) {
			var c = this,
				d = this.options,
				h = this.dataNames,
				j = d.validateElements,
				l = [];
			j = typeof j == "string" ? [j] : j;
			for (i = 0; i < j.length; i++) {
				var m = b.find(j[i] + "[" + d.attribute + "*=" + d.validateIndicator + "]");
				m.each(function() {
					var k = g(this),
						p = k.attr(d.attribute),
						q = c.extractEvents(p, d.eventIndicator);
					k.data(h.validationString, p).data(h.events, q ? q : d.validateEvents)
				});
				l.push(m.get())
			}
			return this.normalizeArray(l)
		},
		fieldsFromObject: function(b, c) {
			var d = this.options,
				h = this.dataNames,
				j = [];
			for (s in c) {
				var l, m;
				if (typeof c[s] == "string") {
					l = c[s];
					m = d.validateEvents
				} else {
					l = c[s][0];
					m = c[s][1]
				}
				var k = b.find(s);
				l = this.mergeValidationString(k, l);
				m = this.mergeEventsString(k, m);
				k.data(h.validationString, d.validateIndicator + "(" + l + ")").data(h.events, m);
				j.push(k.get())
			}
			return this.normalizeArray(j)
		},
		mergeEventsString: function(b, c) {
			var d = b.data(this.dataNames.events),
				h = "";
			if (d) {
				d = d.split(" ");
				for (i = 0; i < d.length; i++) if (c.indexOf(d[i]) == -1) h += " " + d[i]
			}
			return g.trim(c + h)
		},
		mergeValidationString: function(b, c) {
			var d = this.options,
				h = b.data(this.dataNames.validationString),
				j = function(k) {
					var p = k.name;
					if (k.arguments.length) p = p + "(" + k.arguments.join(",") + ")";
					return p
				},
				l = function(k, p) {
					for (i = 0; i < k.length; i++) if (k[i].name == p.name) return true
				};
			if (h) {
				var m = this.extractValidations(d.validateIndicator + "(" + c + ")", d.validateIndicator);
				d = this.extractValidations(h, d.validateIndicator);
				c = "";
				for (o = 0; o < d.length; o++) c += j(d[o]) + ",";
				for (n = 0; n < m.length; n++) l(d, m[n]) || (c += j(m[n]) + ",")
			}
			return c
		},
		bindFormSubmit: function(b) {
			var c = this;
			b.submit(function() {
				return c.allFieldsValid(b, true)
			})
		},
		allFieldsValid: function(b, c) {
			var d = this,
				h = true;
			b.data(this.dataNames.elements).each(function() {
				var j = g(this);
				if (d.validateElement(j, b) != true) {
					c == true && d.triggerValidationEvents(j);
					h = false
				}
			});
			b.trigger("formIs" + (h ? "Valid" : "Invalid"), [b]);
			return h
		},
		bindValidationEvent: function(b, c) {
			var d = this,
				h = this.options,
				j = this.dataNames,
				l = c.data(j.events).split(" ");
			for (i = 0; i < l.length; i++) {
				c.bind("ketchup." + l[i], function() {
					var m = d.validateElement(c, b),
						k = c.data(j.container);
					if (m != true) {
						if (!k) {
							k = h.createErrorContainer(b, c);
							c.data(j.container, k)
						}
						h.addErrorMessages(b, c, k, m);
						h.showErrorContainer(b, c, k)
					} else k && h.hideErrorContainer(b, c, k)
				});
				this.bindValidationEventBridge(c, l[i])
			}
			return this
		},
		bindValidationEventBridge: function(b, c) {
			b.bind(c, function() {
				b.trigger("ketchup." + c)
			})
		},
		validateElement: function(b, c) {
			var d = [],
				h = b.data(this.dataNames.validations),
				j = [c, b, b.val()];
			for (i = 0; i < h.length; i++) h[i].func.apply(this.helpers, j.concat(h[i].arguments)) || d.push(h[i].message);
			c.trigger("fieldIs" + (d.length ? "Invalid" : "Valid"), [c, b]);
			return d.length ? d : true
		},
		elementIsValid: function(b) {
			var c = this.dataNames;
			if (b.data(c.validations)) {
				c = b.parentsUntil("form").last().parent();
				return this.validateElement(b, c) == true ? true : false
			} else if (b.data(c.elements)) return this.allFieldsValid(b);
			return null
		},
		triggerValidationEvents: function(b) {
			for (var c = b.data(this.dataNames.events).split(" "), d = 0; d < c.length; d++) b.trigger("ketchup." + c[d])
		},
		extractValidations: function(b, c) {
			for (var d = b.substr(b.indexOf(c) + c.length + 1), h = "", j = [], l = 0, m = [], k = 0; k < d.length; k++) switch (d.charAt(k)) {
			case "(":
				h += "(";
				l++;
				break;
			case ")":
				if (l) {
					h += ")";
					l--
				} else j.push(g.trim(h));
				break;
			case ",":
				if (l) h += ",";
				else {
					j.push(g.trim(h));
					h = ""
				}
				break;
			default:
				h += d.charAt(k)
			}
			for (v = 0; v < j.length; v++) {
				l = j[v].indexOf("(");
				d = j[v];
				h = [];
				if (l != -1) {
					d = g.trim(j[v].substr(0, l));
					h = g.map(j[v].substr(d.length).split(","), function(p) {
						return g.trim(p.replace("(", "").replace(")", ""))
					})
				}
				if ((l = this.validations[d]) && l.message) {
					k = l.message;
					for (a = 1; a <= h.length; a++) k = k.replace("{arg" + a + "}", h[a - 1]);
					m.push({
						name: d,
						arguments: h,
						func: l.func,
						message: k,
						init: l.init
					})
				}
			}
			return m
		},
		extractEvents: function(b, c) {
			var d = false,
				h = b.indexOf(c + "(");
			if (h != -1) d = b.substr(h + c.length + 1).split(")")[0];
			return d
		},
		normalizeArray: function(b) {
			var c = [];
			for (i = 0; i < b.length; i++) for (e = 0; e < b[i].length; e++) b[i][e] && c.push(b[i][e]);
			return c
		},
		createErrorContainer: function(b, c) {
			if (typeof b == "function") {
				this.defaults.createErrorContainer = b;
				return this
			} else {
				var d = c.offset();
				return g("<div/>", {
					html: "<ul></ul><span></span>",
					"class": "ketchup-error",
					css: {
						top: d.top,
						left: d.left + c.outerWidth() - 20
					}
				}).appendTo("body")
			}
		},
		showErrorContainer: function(b, c, d) {
			if (typeof b == "function") {
				this.defaults.showErrorContainer = b;
				return this
			} else d.show().animate({
				top: c.offset().top - d.height(),
				opacity: 1
			}, "fast")
		},
		hideErrorContainer: function(b, c, d) {
			if (typeof b == "function") {
				this.defaults.hideErrorContainer = b;
				return this
			} else d.animate({
				top: c.offset().top,
				opacity: 0
			}, "fast", function() {
				d.hide()
			})
		},
		addErrorMessages: function(b, c, d, h) {
			if (typeof b == "function") {
				this.defaults.addErrorMessages = b;
				return this
			} else {
				b = d.children("ul");
				b.html("");
				for (i = 0; i < h.length; i++) g("<li/>", {
					text: h[i]
				}).appendTo(b)
			}
		}
	};
	g.fn.ketchup = function(b, c) {
		var d = g(this);
		if (typeof b == "string") switch (b) {
		case "validate":
			g.ketchup.triggerValidationEvents(d);
			break;
		case "isValid":
			return g.ketchup.elementIsValid(d)
		} else this.each(function() {
			g.ketchup.init(d, g.extend({}, g.ketchup.defaults, b), c)
		});
		return this
	}
})(jQuery);
jQuery.ketchup.validation("required", "此字段必需填.", function(g, b, c) {
	g = b.attr("type").toLowerCase();
	return g == "checkbox" || g == "radio" ? b.attr("checked") == true : c.length != 0
}).validation("minlength", "此栏必须的最小字段长度为 {arg1}.", function(g, b, c, d) {
	return c.length >= +d
}).validation("maxlength", "此栏必须的最大字段长度为 {arg1}.", function(g, b, c, d) {
	return c.length <= +d
}).validation("rangelength", "此栏字段长度必须置于 {arg1} 和 {arg2} 之间.", function(g, b, c, d, h) {
	return c.length >= d && c.length <= h
}).validation("min", "必须至少为 {arg1}.", function(g, b, c, d) {
	return this.isNumber(c) && +c >= +d
}).validation("max", "不能够大于 {arg1}.", function(g, b, c, d) {
	return this.isNumber(c) && +c <= +d
}).validation("range", "大小必须至于 {arg1} 和 {arg2} 之间.", function(g, b, c, d, h) {
	return this.isNumber(c) && +c >= +d && +c <= +h
}).validation("number", "必须是一个数字.", function(g, b, c) {
	return this.isNumber(c)
}).validation("digits", "必须是数字.", function(g, b, c) {
	return /^\d+$/.test(c)
}).validation("email", "请填写正确e-mail格式.", function(g, b, c) {
	return this.isEmail(c)
}).validation("url", "必须是 URL.", function(g, b, c) {
	return this.isUrl(c)
}).validation("username", "必须是一个有效的用户名.", function(g, b, c) {
	return this.isUsername(c)
}).validation("match", "必须是 {arg1}.", function(g, b, c, d) {
	return b.val() == d
}).validation("contain", "必须包含 {arg1}", function(g, b, c, d) {
	return this.contains(c, d)
}).validation("date", "必须是一个日期.", function(g, b, c) {
	return this.isDate(c)
}).validation("minselect", "至少选择 {arg1} 个复选框.", function(g, b, c, d) {
	return d <= this.inputsWithName(g, b).filter(":checked").length
}, function(g, b) {
	this.bindBrothers(g, b)
}).validation("maxselect", "选择不超过 {arg1} 个复选框.", function(g, b, c, d) {
	return d >= this.inputsWithName(g, b).filter(":checked").length
}, function(g, b) {
	this.bindBrothers(g, b)
}).validation("rangeselect", "置于 {arg1} 和 {arg2} 复选框之间选择.", function(g, b, c, d, h) {
	g = this.inputsWithName(g, b).filter(":checked").length;
	return d <= g && h >= g
}, function(g, b) {
	this.bindBrothers(g, b)
});
jQuery.ketchup.helper("isNumber", function(g) {
	return /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(g)
}).helper("contains", function(g, b) {
	return g.indexOf(b) != -1
}).helper("isEmail", function(g) {
	return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(g)
}).helper("isUrl", function(g) {
	return /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(g)
}).helper("isUsername", function(g) {
	return /^([a-zA-Z])[a-zA-Z_-]*[\w_-]*[\S]$|^([a-zA-Z])[0-9_-]*[\S]$|^[a-zA-Z]*[\S]$/.test(g)
}).helper("isDate", function(g) {
	return !/Invalid|NaN/.test(new Date(g))
}).helper("inputsWithName", function(g, b) {
	return $('input[name="' + b.attr("name") + '"]', g)
}).helper("inputsWithNameNotSelf", function(g, b) {
	return this.inputsWithName(g, b).filter(function() {
		return $(this).index() != b.index()
	})
}).helper("getKetchupEvents", function(g) {
	g = g.data("events").ketchup;
	var b = [];
	for (i = 0; i < g.length; i++) b.push(g[i].namespace);
	return b.join(" ")
}).helper("bindBrothers", function(g, b) {
	this.inputsWithNameNotSelf(g, b).bind(this.getKetchupEvents(b), function() {
		b.ketchup("validate")
	})
});